﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GTA;

namespace GravityGunScript
{
    public class GravityGun
    {

        public bool isActive; // is gravity gun currenty active
        public bool isAiming; // are we currently aiming with the gravity gun (have pistol in hand & rmb held in)
        public float MaxDistance;// = 100.0F; // max distance from player that we will look for objects to pickup
        public float DetachDistance;// = 20.0F; // Max distance an object can be from our gravity bullet before it is detached
        public eModel BulletModel;// = new Model(0x7F0337DD); // model of the gravity gun bullet
        public float CurrentDistance; // this will hold the current distance the bullet is from player
        public GameObject Bullet; //  this will hold the gravity gun bullet 
        public bool DoesTargetObjectHavePhysics; // is the obj in our gg field dynamic

        public enum GGMode
        {
            Single, // only pickup one obj at a time
            Multiple, // pickup all objs around
        }
        public GGMode Mode;

        public GameObject TargetObject;
        public Vehicle TargetVehicle;
        public Ped TargetPed;


        public GravityGun()
        {
	        isActive=false;
            MaxDistance = 100.0F; // max distance from player that we will look for objects to pickup
            DetachDistance = 20.0F; // Max distance an object can be from our gravity bullet before it is detached
            BulletModel = GTA.eModel.MODEL_CJ_AIRCON10;// GTA.eModel.MODEL_CJ_DINNER_PLATE_1;//GetModelHash("dildo5");// model of the gravity gun bullet
	        TargetObject = new GameObject();
            Mode = GGMode.Single;
        }



        unsafe public GameObject SpawnBullet()
        {
            Log.Debug("GG Spawn Bullet");
            Player pl = new Player();
            Ped plped = pl.GetPlayerPed();
            Vector3 pos = new Vector3(plped.Coordinates.X,plped.Coordinates.Y,plped.Coordinates.Z-30.0F);
	        Bullet = GTA.World.CreateObject(BulletModel,pos);
            if (!GameObject.Exists(Bullet)) { Log.Debug("GG Spawn Bullet: Bullet failed to spawn!"); return null; }

	        float heading;
	        ScriptApi.GetCharHeading(plped,&heading);
	        float CurrentDistance=5.0F;
	        ScriptApi.AttachObjectToPed(Bullet,plped,(ePedBone)1232,CurrentDistance,0.0F,-0.2F,0.0F,0.0F,0.0F,1);
	        if (heading >= 180.0F) {heading -= 180.0F;}
	        else {heading += 180.0F;}
            ScriptApi.SetObjectHeading(Bullet, heading);
            ScriptApi.SetObjectCollision(Bullet, false);
            ScriptApi.SetObjectVisible(Bullet, false);
            ScriptApi.FreezeObjectPosition(Bullet, true);
            Log.Debug("GG Spawn Bullet OK");
            return Bullet;
        }



        public void Start()
        {
            Log.Debug("GG Start");
	        if (isActive) return;
	        Ped player = new Player();
	        if (!isAiming) return;
	        Bullet = SpawnBullet();
            if (!GameObject.Exists(Bullet)) return; // bullet failed to spawn
            GTA.World.AddGetObjectsExclusion(Bullet); // stop world.GetAllObjects from returning the bullet
	        isActive=true;
            Log.Debug("GG Start ok");
        }

        public void Stop()
        {
            Log.Debug("GG Stop");
	        if (ScriptApi.DoesObjectExist(Bullet)) 
	        { // destroy the bullet
                GTA.World.RemoveGetObjectsExclusion(Bullet);
		        ScriptApi.DeleteObject(Bullet);
	        }
	        if (GameObject.Exists(TargetObject))
	        { // we have something held
		        if (!DoesTargetObjectHavePhysics)
		        {
			        ScriptApi.SetObjectCollision(TargetObject,true);
			        ScriptApi.FreezeObjectPosition(TargetObject,true);
		        }
		        else
		        {
			        ScriptApi.SetObjectCollision(TargetObject,true);
			        ScriptApi.FreezeObjectPosition(TargetObject,false);
			        ScriptApi.SetObjectDynamic(TargetObject,true);
		        }
	        }
	        TargetObject = null;
            TargetVehicle = null;
            TargetPed = null;
	        isActive=false;
        }

        unsafe public void Tick()
        {
            if (!isActive) return; ;
            if (!ScriptApi.DoesObjectExist(Bullet)) { Stop(); return; }
            if (!isAiming) { Stop(); return; }
            Player player = new Player();
            Ped plped = player.GetPlayerPed();

            if (Mode == GGMode.Single)
            {
                if (GameObject.Exists(TargetObject))
                { // we are moving an object, static move it
                    uint networkid = 0;
                    ScriptApi.GetNetworkIdFromObject(TargetObject, &networkid);
                    ScriptApi.RequestControlOfNetworkId(networkid);
                    ScriptApi.SetObjectCoordinates(TargetObject, Bullet.Coordinates.X, Bullet.Coordinates.Y, Bullet.Coordinates.Z);
                }
                else if (Vehicle.Exists(TargetVehicle))
                { // moving vehicle 
                    uint networkid = 0;
                    ScriptApi.GetNetworkIdFromVehicle(TargetVehicle, &networkid);
                    ScriptApi.RequestControlOfNetworkId(networkid);
                    Vector3 force = new Vector3();
                    float dist = 0.0F;
                    ScriptApi.GetDistanceBetweenCoords3D(TargetVehicle.Coordinates.X, TargetVehicle.Coordinates.Y, TargetVehicle.Coordinates.Z, Bullet.Coordinates.X, Bullet.Coordinates.Y, Bullet.Coordinates.Z, &dist);
                    if (dist > DetachDistance) { Stop(); return; };
                    force.X += (Bullet.Coordinates.X - TargetVehicle.Coordinates.X) / 2.7F;
                    force.Y += (Bullet.Coordinates.Y - TargetVehicle.Coordinates.Y) / 2.7F;
                    force.Z += (Bullet.Coordinates.Z - TargetVehicle.Coordinates.Z) / 2.7F;

                    float distmod = dist / 4;
                    force.X /= distmod;
                    force.Y /= distmod;
                    force.Z /= distmod;

                    if (dist < 1.0F)
                    {
                        force.X /= 5.0F;
                        force.Y /= 5.0F;
                        force.Z /= 5.0F;
                    }
                    else if (dist < 2.5F)
                    {
                        force.X /= 2.5F;
                        force.Y /= 2.5F;
                        force.Z /= 2.5F;
                    }
                    else if (dist < 5.5F)
                    {
                        force.X /= 1.5F;
                        force.Y /= 1.5F;
                        force.Z /= 1.5F;
                    }

                    ScriptApi.ApplyForceToCar(TargetVehicle, 3, force.X, force.Y, force.Z, 0.0F, 0.0F, 0.0F, 1, 0, 1, 1);

                }
                else if (Ped.Exists(TargetPed))
                { // moving Ped 
                    uint networkid = 0;
                    ScriptApi.GetNetworkIdFromPed(TargetPed, &networkid);
                    ScriptApi.RequestControlOfNetworkId(networkid);
                    float dist = 0.0F;
                    ScriptApi.GetDistanceBetweenCoords3D(TargetPed.Coordinates.X, TargetPed.Coordinates.Y, TargetPed.Coordinates.Z, Bullet.Coordinates.X, Bullet.Coordinates.Y, Bullet.Coordinates.Z, &dist);
                    if (dist > DetachDistance) { Stop(); return; };

                    Vector3 force = new Vector3();
                    force.X += (Bullet.Coordinates.X - TargetPed.Coordinates.X) / 3.0F;
                    force.Y += (Bullet.Coordinates.Y - TargetPed.Coordinates.Y) / 3.0F;
                    force.Z += (Bullet.Coordinates.Z - TargetPed.Coordinates.Z) / 3.0F;
                    ScriptApi.ApplyForceToPed(TargetPed, 3, force.X, force.Y, force.Z, 0.0F, 0.0F, 0.0F, 1, 0, 1, 1);
                }
                else
                { // not moving any objects, search for object to pickup
                    if (CurrentDistance < MaxDistance) CurrentDistance += 2.0F;
                    else CurrentDistance = 5.0F;
                    ScriptApi.AttachObjectToPed(Bullet, plped, (ePedBone)1232, CurrentDistance, 0.0F, -0.2F, 0.0F, 0.0F, 0.0F, 1);

                    TargetVehicle = ScriptApi.GetClosestCar(Bullet.Coordinates.X, Bullet.Coordinates.Y, Bullet.Coordinates.Z, 5.0F, false, 70);
                    if (!Vehicle.Exists(TargetVehicle)) { TargetPed = new Ped(); ScriptApi.GetClosestChar(Bullet.Coordinates.X, Bullet.Coordinates.Y, Bullet.Coordinates.Z, 5.0F, false, false, TargetPed); }
                    //if (!Ped.Exists(TargetPed)) { TargetObject = new GameObject(); TargetObject = World.GetClosestObjectInArea(Bullet.Coordinates, 5.0F); }

                    if (GameObject.Exists(TargetObject))
                    { // we have just found something to pickup
                        bool isstatic = ScriptApi.IsObjectStatic(TargetObject);
                        if (isstatic) { DoesTargetObjectHavePhysics = false; }
                        else { DoesTargetObjectHavePhysics = true; }
                        ScriptApi.SetObjectCollision(TargetObject, false);
                    }
                    else if (Vehicle.Exists(TargetVehicle))
                    { // its a vehicle
                        //ScriptApi.SetCarAsMissionCar(TargetVehicle);
                    }
                    else if (Ped.Exists(TargetPed))
                    { // its a ped
                        //ScriptApi.SetCharAsMissionChar(TargetPed);
                        ScriptApi.SwitchPedToRagdoll(TargetPed, 1000, 30000, 2, 1, 1, 0);
                    }
                }
            }
            else if (Mode == GGMode.Multiple)
            { // Mode == Multi
                Log.Debug("attaching bullet");
                ScriptApi.AttachObjectToPed(Bullet, plped, (ePedBone)1232, 20.0F, 0.0F, -0.2F, 0.0F, 0.0F, 0.0F, 1);
                Log.Debug("moving vehicles");
                foreach (Vehicle vehicle in World.GetAllVehiclesInArea(Bullet.Coordinates, 12.0F))
                { // Vehicles
                    Vector3 force = new Vector3();
                    float dist = 0.0F;
                    ScriptApi.GetDistanceBetweenCoords3D(vehicle.Coordinates.X, vehicle.Coordinates.Y, vehicle.Coordinates.Z, Bullet.Coordinates.X, Bullet.Coordinates.Y, Bullet.Coordinates.Z, &dist);
                    if (dist > DetachDistance) { Stop(); return; };
                    force.X += (Bullet.Coordinates.X - vehicle.Coordinates.X)/2;
                    force.Y += (Bullet.Coordinates.Y - vehicle.Coordinates.Y)/2;
                    force.Z += (Bullet.Coordinates.Z - vehicle.Coordinates.Z)/2;

                    float mod = (float)(2.0 - dist);
                    if (mod > 0)
                    {
                        mod *= 5;
                        force.X /= mod;
                        force.Y /= mod;
                        force.Z /= mod;
                    }
                    mod = (float)(dist/3);
                    force.X /= mod;
                    force.Y /= mod;
                    force.Z /= mod;

                    uint networkid = 0;
                    ScriptApi.GetNetworkIdFromVehicle(vehicle, &networkid);
                    ScriptApi.RequestControlOfNetworkId(networkid);
                    ScriptApi.ApplyForceToCar(vehicle, 3, force.X, force.Y, force.Z, 0.0F, 0.0F, 0.0F, 1, 0, 1, 1);

                }
                Log.Debug("moving peds");
                foreach (Ped ped in World.GetAllPedsInArea(Bullet.Coordinates, 12.0F))
                { // Peds
                    float dist = 0.0F;
                    ScriptApi.GetDistanceBetweenCoords3D(ped.Coordinates.X, ped.Coordinates.Y, ped.Coordinates.Z, Bullet.Coordinates.X, Bullet.Coordinates.Y, Bullet.Coordinates.Z, &dist);
                    if (dist > DetachDistance) { Stop(); return; };

                    Vector3 force = new Vector3();
                    force.X += (Bullet.Coordinates.X - ped.Coordinates.X)/3;
                    force.Y += (Bullet.Coordinates.Y - ped.Coordinates.Y)/3;
                    force.Z += (Bullet.Coordinates.Z - ped.Coordinates.Z)/3;

                    float mod = (float)(2.0 - dist);
                    if (mod > 0)
                    {
                        mod *= 5;
                        force.X /= mod;
                        force.Y /= mod;
                        force.Z /= mod;
                    }
                    mod = (float)(dist/3);
                    force.X /= mod;
                    force.Y /= mod;
                    force.Z /= mod;

                    uint networkid = 0;
                    ScriptApi.GetNetworkIdFromPed(ped, &networkid);
                    ScriptApi.RequestControlOfNetworkId(networkid);
                    ScriptApi.ApplyForceToPed(ped, 3, force.X, force.Y, force.Z, 0.0F, 0.0F, 0.0F, 1, 0, 1, 1);
                }
                Log.Debug("moving objs");
                foreach (GameObject obj in World.GetAllObjectsInArea(Bullet.Coordinates, 12.0F))
                { // Objects

                    float dist = 0.0F;
                    ScriptApi.GetDistanceBetweenCoords3D(obj.Coordinates.X, obj.Coordinates.Y, obj.Coordinates.Z, Bullet.Coordinates.X, Bullet.Coordinates.Y, Bullet.Coordinates.Z, &dist);
                    if (dist > DetachDistance) { Stop(); return; };
                    //Log.Debug("moving1");

                    Vector3 force = new Vector3();
                    force.X += (Bullet.Coordinates.X - obj.Coordinates.X)/3;
                    force.Y += (Bullet.Coordinates.Y - obj.Coordinates.Y)/3;
                    force.Z += (Bullet.Coordinates.Z - obj.Coordinates.Z)/3;

                    float mod = (float)(2.0 - dist);
                    if (mod > 0)
                    {
                        mod *= 5;
                        force.X /= mod;
                        force.Y /= mod;
                        force.Z /= mod;
                    }
                    mod = (float)(dist/3);
                    force.X /= mod;
                    force.Y /= mod;
                    force.Z /= mod;
                    //uint networkid = 0;
                    //ScriptApi.GetNetworkIdFromObject(obj, &networkid);
                    //ScriptApi.RequestControlOfNetworkId(networkid);
                    Log.Debug("moving3");
                    if (!GameObject.Exists(obj)) continue;
                    GTA.eModel mod2 = eModel.MODEL_ADMIRAL;
                    ScriptApi.GetObjectModel(obj, &mod2);
                    Log.Debug("obj model: " + mod2.ToString() + ", force: " + force.X.ToString() + ", " + force.Y.ToString() + ", " + force.Z.ToString());
                    ScriptApi.ApplyForceToObject(obj, 3, force.X, force.Y, force.Z, 0.0F, 0.0F, 0.0F, 1, 0, 1, 1);
                    Log.Debug("moving4");
                }
                Log.Debug("moving objs done");
            }
        }
    }
};
